﻿using System;
using System.Collections.Generic;
using System.Drawing;

using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Features2D;
using System.ComponentModel;
using iMapa.Utils;
using iMapa.UI.ModelView.Common;
using System.Xml.Serialization;
using iMapa.Helper;
using ActionPluginAPI;
using iMapa.Backend.ActionPlugin;

namespace iMapa.Backend
{
    [XmlRootAttribute("InteractMap", Namespace = "", IsNullable = false)]
    public class InteractMap : ViewModelBase
    {
        private String name;
        private Image<Bgr, Byte> modelImage;
        private List<InteractObject> interactObjects=new List<InteractObject>();
        private string parentGroup;

        //runtime
        private SURFFeature[] modelFeatures;
        private PointF[] modelImageCornerPoints;
        private Queue<PointF[]> foundImageCornerPointsQueue;
        private HomographyMatrix HmatrixToModel;
        private HomographyMatrix HmatrixToObserved;
        private int distanceSum=0;


        public InteractMap()
        {
        }

        public InteractMap(Image<Bgr, Byte> model,string group)
        {
            modelImage = model;
            this.parentGroup = group;
            InitializeInteractMap();
        }

        public void InitializeInteractMap()
        {
            Rectangle rect = modelImage.ROI;
            modelImageCornerPoints = ImgUtils.getCornerPoints(ModelImage.Size);

            foundImageCornerPointsQueue = new Queue<PointF[]>(ConfigurationConstants.INTERACT_MAP_HOMOGRAPHY_QUEUE_LENGTH);

            SURFDetector detector = ConfigurationConstants.SURFPARAMS;
            modelFeatures = modelImage.Convert<Gray, Byte>().ExtractSURF(ref detector);
            LoadActions();
        }

        public void Enqueue(PointF[] m)
        {
            
            foundImageCornerPointsQueue.Enqueue(m);
            if (foundImageCornerPointsQueue.Count > ConfigurationConstants.INTERACT_MAP_HOMOGRAPHY_QUEUE_LENGTH-1)
            {
                foundImageCornerPointsQueue.Dequeue();
            }

            List<PointF[]> matched = new List<PointF[]>();
            distanceSum=0;
            foreach (PointF[] p in foundImageCornerPointsQueue)
            {
                if (p != null && m!=null)
                {
                    distanceSum += (int)ImgUtils.CountAvgDistance(m, p);
                    if (ImgUtils.CountAvgDistance(m, p) < 6)
                    {
                        matched.Add(p);
                    }
                }
            }
            onPropertyChanged("Progress");

            if (matched.Count >= 2)
            {
                PointF[] avg = PointsAvg(matched);
                lock (this)
                {
                    HmatrixToObserved = ImgUtils.CountHomography(modelImageCornerPoints, avg);
                    HmatrixToModel = ImgUtils.CountHomography(avg, modelImageCornerPoints);
                    onCalibrationCanged();
                }
            }
            
        }


        public void resetHomography()
        {
            lock (this)
            {
                foundImageCornerPointsQueue.Clear();
                HmatrixToModel = null;
                HmatrixToObserved = null;
                distanceSum = 0;
                onCalibrationCanged();
                onPropertyChanged("Progress");
            }
        }

        public PointF[] CountProjectionPoints(PointF[] observedPoints, Boolean observerToModel)
        {
            lock (this)
            {
                if (HmatrixToModel != null)
                {
                    PointF[] f = (PointF[])observedPoints.Clone();
                    HomographyMatrix matrix = observerToModel ? HmatrixToModel : HmatrixToObserved;
                    matrix.ProjectPoints(f);
                    return f;
                }
                else
                {
                    return null;
                }
            }
        }

        public PointF? CountProjectionPoint(PointF observerPoint, Boolean observerToModel)
        {
            lock (this)
            {
                if (HmatrixToModel != null)
                {
                    PointF[] pts = { observerPoint };
                    HomographyMatrix matrix = observerToModel ? HmatrixToModel : HmatrixToObserved;
                    matrix.ProjectPoints(pts);
                    return pts[0];
                }
                else
                {
                    return null;
                }
            }
        }




        private PointF[] PointsAvg(List<PointF[]> matched)
        {
            PointF[] avgpts = new PointF[] { 
                    new PointF(0,0),
                    new PointF(0,0),
                    new PointF(0,0),
                    new PointF(0,0)};

            foreach (PointF[] f in matched)
            {
                for (int i = 0; i < 4; i++)
                {
                    avgpts[i].X += (f[i].X / matched.Count);
                    avgpts[i].Y += (f[i].Y / matched.Count);
                }
            }
            return avgpts;
        }

        private void onCalibrationCanged()
        {
            onPropertyChanged("CalibratedVisibility");
            onPropertyChanged("NotCalibratedVisibility");
        }

        [XmlAttributeAttribute()]
        public string ParentGroup
        {
            get { return parentGroup; }
            set { parentGroup = value; }
        } 

        public Image<Bgr, Byte> ModelImage
        {
            get { return modelImage; }
            set { modelImage = value; }
        }
        [XmlIgnoreAttribute()]
        public HomographyMatrix HMatrixToObserved
        {
            get { return HmatrixToObserved; }
        }

        [XmlIgnoreAttribute()]
        public SURFFeature[] ModelFeatures
        {
            get { return modelFeatures; }
        }

        [XmlIgnoreAttribute()]
        public int Progress
        {
            get 
            {
                int count=Utilities.CountOfNotNullElements(foundImageCornerPointsQueue);
                if (count > 1 && distanceSum!=0)
                {
                    int avgDist = distanceSum / Utilities.CountOfNotNullElements(foundImageCornerPointsQueue);
                    int y = -avgDist + 100;
                    y = y > 100 ? 100 : y;
                    y = y < 0 ? 0 : y;
                    return y;
                }
                else
                {
                    return 0;
                }
            }
        }
        
        [XmlAttributeAttribute()]
        public String Name
        {
            get { return name; }
            set { name = value; }
        }
        
        [XmlArray("InteractObjects"),XmlArrayItem("InteractObject", typeof(InteractObject))]
        public List<InteractObject> InteractObjects
        {
            get { return interactObjects; }
            set { interactObjects = value; }
        }


        public void LoadActions()
        {
            foreach (InteractObject io in this.InteractObjects)
            {
                string actionType = io.ActionType;
                if (io.ActionParams == null && actionType != null)
                {
                    PluginHandler handler = PluginHandler.getInstance();
                    IActionPlugin plugin = handler.GetPluginsByName(actionType);
                    if (io.ActionParamsFile !=null)
                    {
                        io.ActionParams = plugin.LoadFromFile(InteractMapLoader.GetInteractMapPath(Name) + io.ActionParamsFile);
                    }
                    else
                    {
                        io.ActionParams = plugin.GetNewParametersInstance(InteractMapLoader.GetInteractMapPath(Name));
                    }
                }
            }
            
        }

    }
}
